﻿using IScheduler.Core.Properties;
using System;
using System.Configuration;
using System.Data;
using log4net.Core;
using log4net.Util;

namespace IScheduler.Core.Persistence.Base
{
    public class DataAccessLayer : IDisposable
    {

        #region Public Instance Constructors

        public DataAccessLayer()
        {
        	try
        	{
	            ConnectionType  = GetConnectionType();
	            ConnectionString = GetConnectionString();
	            SecurityContext = SecurityContextProvider.DefaultProvider.CreateSecurityContext(this);
	            Open();
        	}
        	catch(Exception e)
        	{
        		throw e;
        	}
        }

        ~DataAccessLayer()
        {
            CleanUp(true);
        }

        #endregion // Public Instance Constructors

        #region Public Instance 
		
        /// <summary>
        /// Try to parse the reader value object to Datetime.
        /// To think in a better solution.
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public DateTime TryParseReaderToDateTime(object data)
        {
        	return DateTime.Parse(data.ToString());
        }        
        
        /// <summary>
        /// Try to parse the reader value object to int.
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public int TryParseReaderToInt(object data)
        {
        	int value = 0;
    		if (data != DBNull.Value)
    		{
    			int.TryParse(data.ToString(), out value);
    		}
    		return value;
        }

        /// <summary>
        /// Try to parse the reader value object to string.
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public string TryParseReaderToString(object data)
        {
        	string value="";
    		if (data != DBNull.Value)
    		{
    			value = data.ToString().Trim();
    		}
    		return value;
        }        
        
        /// <summary>
        /// Try to parse the reader value object to bool.
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public bool TryParseReaderToBool(object data)
        {
        	bool value=false;
    		if (data != DBNull.Value)
    		{
    			bool.TryParse(data.ToString().Trim(), out value);
    		}
    		return value;
        }          
        
        #endregion // Public Instance

        #region Private Instance Properties

        /// <summary>
        /// Used to determine if Dispose() has already been called.
        /// </summary>
        private bool disposed = false;
        
        /// <summary>
        /// Gets or sets the underlying <see cref="IDbConnection" />.
        /// </summary>
        /// <value>
        /// The underlying <see cref="IDbConnection" />.
        /// </value>
        /// <remarks>
        /// <see cref="AdoNetAppender" /> creates a <see cref="IDbConnection" /> to insert 
        /// logging events into a database.  Classes deriving from <see cref="AdoNetAppender" /> 
        /// can use this property to get or set this <see cref="IDbConnection" />.  Use the 
        /// underlying <see cref="IDbConnection" /> returned from <see cref="Connection" /> if 
        /// you require access beyond that which <see cref="AdoNetAppender" /> provides.
        /// </remarks>
        protected IDbConnection Connection { get; set; }  
        
        #endregion //  Private Instance Properties

        #region Private Instance Functions


        private void CleanUp(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    Close();
                }
            }
            disposed = true;
        }

        /// <summary>
        /// Override the parent method to close the database
        /// </summary>
        /// <remarks>
        /// <para>
        /// Closes the database command and database connection.
        /// </para>
        /// </remarks>
        private void Close()
        {
            try
            {
                if (Connection != null)
                {
                    try
                    {
                        Connection.Close();
                    }
                    catch (Exception ex)
                    {
                        LoggingService.Warn("DataAccessLibrary: Exception while disposing cached connection object.", ex);
                        
                    }
                    Connection = null;
                }

                LoggingService.Debug("The current connection was finalized successful.");
            }
            catch (Exception e)
            {
                // Sadly, your connection string is bad.
                Connection = null;
                throw new DataAccessException(string.Format(Resources.DataAccessLayerExceptionCloseDatabase, ConnectionString), this.ToString(), e);
            }
        }

        /// <summary>
        /// Connects to the database.
        /// </summary>	
        private void Open()
        {
            try
            {
                if (Connection != null)
                {
                    try
                    {
                        Connection.Close();
                        Connection.Dispose();
                    }
                    catch (Exception ex)
                    {
                        LoggingService.Warn("AdoNetAppender: Exception while disposing cached connection object", ex);
                    }
                    Connection = null;
                }

                // Create the connection object
                Connection = (IDbConnection)Activator.CreateInstance(ResolveConnectionType());

                // Set the connection string
                Connection.ConnectionString = ConnectionString;

                using (SecurityContext.Impersonate(this))
                {
                    // Open the database connection
                    Connection.Open();
                }

                LoggingService.Debug("The current connection was initialized successful.");
            }
            catch (Exception e)
            {
                // Sadly, your connection string is bad.
                Connection = null;
                throw new DataAccessException(string.Format(Resources.DataAccessLayerExceptionOpenDatabase, ConnectionString), this.ToString(), e);
            }
            
        }

        /// <summary>
        /// Retrieves the class type of the ADO.NET provider.
        /// </summary>
        /// <remarks>
        /// <para>
        /// Gets the Type of the ADO.NET provider to use to connect to the
        /// database. This method resolves the type specified in the 
        /// <see cref="ConnectionType"/> property.
        /// </para>
        /// <para>
        /// Subclasses can override this method to return a different type
        /// if necessary.
        /// </para>
        /// </remarks>
        /// <returns>The <see cref="Type"/> of the ADO.NET provider</returns>
        private Type ResolveConnectionType()
        {
            try
            {
                return SystemInfo.GetTypeFromString(ConnectionType, true, false);
            }
            catch (Exception e)
            {
                throw new DataAccessException(string.Format(Resources.DataAccessLayerExceptionResolveConnectionType, ConnectionType), this.ToString(), e);
            }
        }

        /// <summary>
        /// Retrieves the connection string type of the app.config
        /// </summary>
        /// <remarks>
        /// <para>
        /// Gets the connection string to use to connect to the
        /// database. This method resolves the connection string type specified in the 
        /// <see cref="String"/> property.
        /// </para>
        /// <para>
        /// Subclasses can override this method to return a different type
        /// if necessary.
        /// </para>
        /// </remarks>
        /// <returns>The <see cref="Type"/> of the ADO.NET provider</returns>
        private string GetConnectionType()
        {
            try
            {
                ConnectionStringSettings cs = ConfigurationManager.ConnectionStrings[SettingService.Settings.DatabaseDefault];
                ConnectionType = cs.ProviderName;
                return ConnectionType;
            }
            catch (Exception ex)
            {
                LoggingService.ErrorFormatted(string.Format("Failed to resolve connection type [{0}].", ConnectionType), ex);
                throw ex;
            }
            finally
            {
                LoggingService.DebugFormatted(string.Format("The current connection string is [{0}].", ConnectionType));
            }
        }

        /// <summary>
        /// Retrieves the connection string of the app.config
        /// </summary>
        /// <remarks>
        /// <para>
        /// Gets the connection string to use to connect to the
        /// database. This method resolves the connection string specified in the 
        /// <see cref="String"/> property.
        /// </para>
        /// <para>
        /// Subclasses can override this method to return a different type
        /// if necessary.
        /// </para>
        /// </remarks>
        /// <returns>The <see cref="Type"/> of the ADO.NET provider</returns>
        private string GetConnectionString()
        {
            try
            {
                ConnectionStringSettings cs = ConfigurationManager.ConnectionStrings[SettingService.Settings.DatabaseDefault];
                ConnectionString = cs.ConnectionString;
                return ConnectionString;
            }
            catch (Exception ex)
            {
                LoggingService.ErrorFormatted(string.Format("Failed to resolve connection string [{0}].", ConnectionString), ex);
                throw ex;
            }
            finally
            {
                LoggingService.DebugFormatted(string.Format("The current connection string is [{0}].", ConnectionString));
            }
        }

        #endregion // Private Instance Functions
        
        #region Protected Instance
        
        
        /// <summary>
        /// Get command prepared query.
        /// </summary>
        /// <param name="query"></param>
        /// <returns></returns>
        protected IDbCommand GetCommandQuery(string query)
        {
        	try
        	{
	        	IDbCommand command = this.Connection.CreateCommand();
	        	command.CommandText = query;
	        	command.CommandType = CommandType.Text;
	        	return command;
        	}
        	catch(DataAccessException e)
        	{
        		throw new DataAccessException(string.Format(Resources.DataAccessLayerExceptionGetCommand, query), this.ToString(), e);
        	}
        }
        
        /// <summary>
        /// Gets or sets the database connection string that is used to connect to 
        /// the database.
        /// </summary>
        /// <value>
        /// The database connection string used to connect to the database.
        /// </value>
        /// <remarks>
        /// <para>
        /// The connections string is specific to the connection type.
        /// See <see cref="ConnectionType"/> for more information.
        /// </para>
        /// </remarks>
        /// <example>Connection string for MS Access via ODBC:
        /// <code>"DSN=MS Access Database;UID=admin;PWD=;SystemDB=C:\data\System.mdw;SafeTransactions = 0;FIL=MS Access;DriverID = 25;DBQ=C:\data\train33.mdb"</code>
        /// </example>
        /// <example>Another connection string for MS Access via ODBC:
        /// <code>"Driver={Microsoft Access Driver (*.mdb)};DBQ=C:\Work\cvs_root\log4net-1.2\access.mdb;UID=;PWD=;"</code>
        /// </example>
        /// <example>Connection string for MS Access via OLE DB:
        /// <code>"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Work\cvs_root\log4net-1.2\access.mdb;User Id=;Password=;"</code>
        /// </example>
        protected string ConnectionString { get; set; }

        /// <summary>
        /// Gets or sets the type name of the <see cref="IDbConnection"/> connection
        /// that should be created.
        /// </summary>
        /// <value>
        /// The type name of the <see cref="IDbConnection"/> connection.
        /// </value>
        /// <remarks>
        /// <para>
        /// The type name of the ADO.NET provider to use.
        /// </para>
        /// <para>
        /// The default is to use the OLE DB provider.
        /// </para>
        /// </remarks>
        /// <example>Use the OLE DB Provider. This is the default value.
        /// <code>System.Data.OleDb.OleDbConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</code>
        /// </example>
        /// <example>Use the MS SQL Server Provider. 
        /// <code>System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</code>
        /// </example>
        /// <example>Use the ODBC Provider. 
        /// <code>Microsoft.Data.Odbc.OdbcConnection,Microsoft.Data.Odbc,version=1.0.3300.0,publicKeyToken=b77a5c561934e089,culture=neutral</code>
        /// This is an optional package that you can download from 
        /// <a href="http://msdn.microsoft.com/downloads">http://msdn.microsoft.com/downloads</a> 
        /// search for <b>ODBC .NET Data Provider</b>.
        /// </example>
        /// <example>Use the Oracle Provider. 
        /// <code>System.Data.OracleClient.OracleConnection, System.Data.OracleClient, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</code>
        /// This is an optional package that you can download from 
        /// <a href="http://msdn.microsoft.com/downloads">http://msdn.microsoft.com/downloads</a> 
        /// search for <b>.NET Managed Provider for Oracle</b>.
        /// </example>
        protected string ConnectionType { get; set; }
        
        /// <summary>
        /// Gets or sets the <see cref="SecurityContext"/> used to call the NetSend method.
        /// </summary>
        /// <value>
        /// The <see cref="SecurityContext"/> used to call the NetSend method.
        /// </value>
        /// <remarks>
        /// <para>
        /// Unless a <see cref="SecurityContext"/> specified here for this appender
        /// the <see cref="SecurityContextProvider.DefaultProvider"/> is queried for the
        /// security context to use. The default behavior is to use the security context
        /// of the current thread.
        /// </para>
        /// </remarks>
        protected SecurityContext SecurityContext { get; set; }
        

        #endregion //  Protected Instance
        
        #region IDisposable Members

        public void Dispose()
        {
            CleanUp(true);
            GC.SuppressFinalize(this);
        }

        void IDisposable.Dispose()
        {
            Dispose();
        }

        #endregion
    }
}
